<script setup lang="ts">
import { onMounted, ref, reactive, onBeforeUnmount, onUnmounted } from 'vue'
import * as THREE from 'three'
import * as TWEEN from 'tween' // 动画过度库

import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import { CSS2DRenderer } from 'three/examples/jsm/renderers/CSS2DRenderer'
import { useRouter } from 'vue-router'

import { quName, quCode, lon2xyz } from './Basic'
import { infoBallTexTure, infoBall } from './Earth'
import {
  earthAnimation,
  earthApertureTexTure,
  sweepLightFn,
  sweepLightTexTure,
} from './AnimationScene'
import { createPoint } from './AddAreaPoint'
import {
  addPointMark,
  clearMarks,
  infoMarkTexTure,
  meshAnimate,
} from './AddPointMark'
import { addFlyLine, flyLineAnimation, clearFlyLines } from './FlyLine'
import { changeCamera } from './ChangeCamera'
import { mouseClick } from './MouseUtil'

const router = useRouter()
const currentPath = router.currentRoute.value.path

let camera: any, scene: any, controls: any
let group = new THREE.Group() as any
let radius = 100
let fov = 100

// 地球基本配置信息
const data = reactive({
  mapDom: null,
  renderer: null,
  labelRenderer: null,
  animationType: true, // 地球入场动画
  rotationY: false, // 地球自动旋转
  meshAnimateType: true, // 标记点动画
  lonlat: { x: 0, y: 150, z: -350 }, // 相机初始化位置
  countryName: null, // 数据
})

// 地球基本配置信息
const mapId = ref(null)
const threeInfo = {} as any
const infoThree = () => {
  // 场景
  scene = new THREE.Scene()
  // 渲染
  data.renderer = new THREE.WebGLRenderer({
    antialias: true, // 抗锯齿
    alpha: true, // 背景透明
    preserveDrawingBuffer: true, // 保存绘图缓冲
  })
  data.mapDom = mapId
  data.renderer.setSize(data.mapDom.clientWidth, data.mapDom.clientHeight)
  data.renderer.setClearColor(0x000, 0)
  data.mapDom.appendChild(data.renderer.domElement)

  // 标签渲染
  data.labelRenderer = new CSS2DRenderer()
  data.labelRenderer.setSize(data.mapDom.clientWidth, data.mapDom.clientHeight)
  data.labelRenderer.domElement.style.position = 'absolute'
  data.labelRenderer.domElement.style.top = '0px'
  document.body.appendChild(data.labelRenderer.domElement)

  // 相机
  camera = new THREE.PerspectiveCamera(
    fov,
    data.mapDom.clientWidth / data.mapDom.clientHeight,
    1,
    1000
  )
  // camera.position.set(0, 30, -250) // 设置相机位置 中国
  // camera.lookAt(0, 0, 0) // 设置相机方向(指向的场景对象)

  // 配置信息
  threeInfo.data = data
  threeInfo.camera = camera
  threeInfo.scene = scene
  threeInfo.controls = controls
  threeInfo.group = group
  threeInfo.radius = radius
  threeInfo.fov = fov
  threeInfo.quCode = quCode
  threeInfo.quName = quName
  threeInfo.currentPath = currentPath

  infoOrbitControls() // 鼠标控制器
}

// 重新渲染
const infoRender = () => {
  if (data.renderer) {
    data.renderer.clear() // 清除渲染器
    if (data.animationType) ballAnimation() // 地球入场动画
    if (data.rotationY) ballRotationY() // 地球自动旋转
    if (data.meshAnimateType) meshAnimate(threeInfo) // 标记点动画
    data.renderer.render(scene, camera)
    data.labelRenderer.render(scene, camera)
    requestAnimationFrame(infoRender)
    sweepLightFn() // 扫光
    flyLineAnimation() // 飞线运动函数
    TWEEN.update()
  }
}

// 鼠标控制器
const infoOrbitControls = () => {
  if (data.labelRenderer) {
    const dom = data.labelRenderer.domElement
    controls = new OrbitControls(camera, dom)
    controls.enableDamping = false // 是否开启阻尼
    controls.enableZoom = true // 是否开启鼠标滚轮缩放
    controls.autoRotate = false // 是否开启自动旋转
    controls.autoRotateSpeed = 30 // 设置自动旋转速度
    controls.enablePan = false // 是否开启右键拖拽
    controls.minDistance = 170 // 设置相机距离原点的最近距离
    controls.maxDistance = 350 // 设置相机距离原点的最远距离
    // 添加自定义class
    controls.domElement.classList.add('orbit-controls')
    // 添加自定义样式
    controls.domElement.style.zIndex = '9'
    // 放入指定 dom
    data.mapDom.appendChild(controls.domElement)
  }
}

// 地球入场动画
const ballAnimation = () => {
  if (data.mapDom) {
    let cameraPosition = [data.lonlat.x, data.lonlat.y, data.lonlat.z]
    fov -= 2
    if (fov <= 45) {
      data.animationType = false
      camera.position.set(...cameraPosition) // 地球相机起始点
      camera.lookAt(0, 0, 0)
      infoOrbitControls()
    } else {
      camera = new THREE.PerspectiveCamera(
        fov,
        data.mapDom.clientWidth / data.mapDom.clientHeight,
        1,
        1000
      )
      camera.position.set(...cameraPosition) // 地球相机起始点
      camera.lookAt(0, 0, 0)
    }
    // if (currentPath === '/finance') {
    //   mapStore().setInitTime(5000)
    // } else {
    //   mapStore().setInitTime(3000)
    // }
  }
}

// 地球自动旋转
const ballRotationY = () => {
  data.rotationY = true
  scene.rotation.y += 0.001
}

// 添加图片点位标签
const createAreaPoint = (areas) => {
  // 循环创建地标，文字标签
  const config = {
    name: areas.name,
    perData: areas.perData,
    scopeCode: areas.scope_code,
    scopeLevel: areas.scope_level,
    isClick: areas.isClick,
    position: lon2xyz(radius + 5, areas.lon, areas.lat),
    scaleXY: 100,
  }
  createPoint(config, threeInfo) // 精灵标签函数
}

// 添加纹理标记点(国家数据坐标光圈)
const infoMark = (item) => {
  addPointMark(item, threeInfo)
}

// 清除纹理标记
const clearInfoMarks = () => {
  clearMarks(threeInfo)
}

// 添加飞线
const createFlyLine = (data) => {
  addFlyLine(data, threeInfo)
}

// 移动相机
const cameraPos = (objList) => {
  changeCamera(objList, camera, data, scene)
}

// 鼠标事件（点击标记的点的事件）
const infoMouse = (event) => {
  event.preventDefault() // 阻止默认事件
  const raycaster = new THREE.Raycaster()
  const mouse = new THREE.Vector2()
  // 通过鼠标点击位置,计算出 raycaster 所需点的位置,以屏幕为中心点,范围 -1 到 1
  let getRect = data.mapDom.getBoundingClientRect()
  mouse.x = ((event.clientX - getRect.left) / data.mapDom.offsetWidth) * 2 - 1
  mouse.y = -((event.clientY - getRect.top) / data.mapDom.offsetHeight) * 2 + 1
  //通过鼠标点击的位置(二维坐标)和当前相机的矩阵计算出射线位置
  raycaster.setFromCamera(mouse, camera)
  // 获取与射线相交的对象数组，其中的元素按照距离排序，越近的越靠前
  let intersects = raycaster.intersectObjects(scene.children)
  // 开启自动旋转
  data.rotationY = !data.rotationY
  // 点击对象的处理
  mouseClick(intersects, threeInfo, cameraPos)
}

// 删除所有标记点
const delAll = () => {
  // frameDivClose()
  group.traverse((item: any) => {
    if (item.type === 'Mesh') {
      item.geometry.dispose()
      item.material.dispose()
    }
  })
  scene.remove(group)
  // 删除group中的children
  if (group.children && group.children.length > 0) {
    let i = 0
    for (i; i < group.children.length; i++) {
      group.remove(group.children[i])
    }
  }
}

// 删除指定标记点
const delMark = (item) => {
  // frameDivClose()
  let layerObj = scene.getObjectByName(item.name)
  group.remove(layerObj)
}

// 开启或关闭地球自动旋转
const rotation = (rotationY) => {
  data.rotationY = rotationY
}

// 清理素材
const removeModel = (parent: any, child: any) => {
  if (child.children.length) {
    let arr = child.children.filter((x: any) => x)
    arr.forEach((a: any) => {
      removeModel(child, a)
    })
  }
  if (child instanceof THREE.Mesh || child instanceof THREE.Line) {
    if (child.material.map) child.material.map.dispose()
    child.material.dispose()
    child.geometry.dispose()
  } else if (child.material) {
    child.material.dispose()
  }
  child.remove()
  if (parent) {
    parent.remove(child)
  }
}
// 通过遍历场景中所有对象的材质，查找并解除它们对纹理的引用
const disposeTextureFromScene = (texture: any) => {
  scene.traverse((obj: { material: { map: null } }) => {
    if (obj.material && obj.material.map === texture) {
      obj.material.map = null
    }
  })
}
// 清除纹理贴图数据
function disposeTexture(texture: any) {
  disposeTextureFromScene(texture) // 清除场景中的纹理贴图引用
  // texture.dispose() // 清除纹理数据
  texture = null // 清除纹理对象的引用
}

// 清理内存 camera, scene, controls, mesh
const disposeAll = () => {
  try {
    removeModel(null, scene) // 删除场景中的所有模型
    delAll() // 删除所有标记点

    disposeTexture(infoBallTexTure) // 信息球纹理
    disposeTexture(earthApertureTexTure) // 地球光圈纹理
    disposeTexture(sweepLightTexTure) // 扫光纹理
    disposeTexture(infoMarkTexTure) // 信息标记纹理

    controls.dispose() // 释放controls变量的内存

    data.labelRenderer.document = null //labelRenderer的文档删除
    data.labelRenderer = null //释放labelRenderer变量的内存

    data.renderer.renderLists.dispose() // 释放renderLists变量的内存
    data.renderer.forceContextLoss() // 模拟WebGL环境的丢失。
    data.renderer.dispose() // 释放renderer变量的内存
    data.renderer.domElement = null //renderer的渲染容器删除
    data.renderer = null // 渲染器

    data.mapDom = null // 地图容器
    data.animationType = true // 地球入场动画
    data.rotationY = false // 地球自动旋转
    data.meshAnimateType = true // 标记点动画
    data.lonlat = { x: 0, y: 0, z: -350 } // 相机初始化位置
    data.countryName = null // 数据

    //清除所有缓存中的值。
    THREE.Cache.clear()
  } catch (error) {
    console.log('🚀 ~ 清理内存异常:', error)
  }
}

// 初始化
const info = () => {
  infoThree() // 基本配置
  infoBall(threeInfo) // 创建地球
  earthAnimation(threeInfo) // 动画场景
  infoRender() // 重新渲染
  // data.renderer.domElement.addEventListener('click', infoMouse)
  data.labelRenderer.domElement.addEventListener('click', infoMouse)
}

onMounted(() => {
  info()
})

onBeforeUnmount(() => {
  // console.log('销毁前：', data.renderer?.info.memory)
  // disposeAll()
})

onUnmounted(() => {
  disposeAll()
  // console.log('销毁后：', data.renderer?.info.memory)
})

// 暴露方法
defineExpose({
  infoMark, // 初始化标记
  clearInfoMarks, // 清除标记
  cameraPos, // 相机位置
  delAll, // 清除所有标记点
  delMark, // 清除单个标记点
  rotation, // 开启或关闭地球自动旋转
  createFlyLine, // 创建飞线
  createAreaPoint, // 创建区域点
  clearFlyLines, // 清除飞线
  // frameDivClose, // 关闭弹窗
  // infoColumn, // 柱状图
  // createLabelObj, // 创建标签
})

// 监听窗口变化重置相机
window.addEventListener('resize', () => {
  camera.aspect = window.innerWidth / window.innerHeight
  camera.updateProjectionMatrix()
  if (data.renderer !== null && data.renderer.setSize) {
    data.renderer.setSize(window.innerWidth, window.innerHeight)
    data.labelRenderer.setSize(window.innerWidth, window.innerHeight)
  }
})
</script>

<template>
  <div>
    <div id="layerMain" v-if="false">
      <div>{{ data.countryName }}</div>
      <div class="shape" />
    </div>
    <div ref="mapId" class="mapId" id="mapId" />
  </div>
</template>

<style lang="scss" scoped>
#mapId {
  width: 100vw;
  // height: calc(100vh - 75px);
  height: 100vh;
  z-index: 1;
}

#layerMain {
  position: absolute;
  width: 300px;
  height: 160px;
  line-height: 160px;
  text-align: center;
  color: white;
  display: none;
  background-color: rgba(34, 34, 35, 0.6);

  .shape {
    position: absolute;
    margin: auto;
    left: 0;
    right: 0;
    width: 0;
    height: 0;
    bottom: -40px;
    border: 20px solid transparent;
    border-top-color: rgba(34, 34, 35, 0.6);
  }
}
</style>
<style lang="scss">
.label_count {
  background-color: rgba(0, 0, 0, 0.5);
  color: #89c3f4;
  font-size: 12px;
  font-weight: bold;
  font-family: 微软雅黑;
  padding: 5px;
  border-radius: 3px;
  border: 1px solid #1e80ff;

  div {
    margin-bottom: 5px;
  }
}

.permissionTip {
  background-color: rgba(23, 96, 190, 0.65);
  border: #1e80ff solid 1px;

  .el-message-icon--info,
  .el-message__content,
  .el-message__closeBtn {
    color: #fff !important;
  }
}
</style>
